package org.beyondhc.lessons.observer.genericwithonearg;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

/**
 * Type-safe observable that enables using observable-observer pattern without
 * passing plain Objects in updates. Based on the code of the original Java
 * Observable by Chris Warth.
 * 
 * @author Jaana Kulmala
 * 
 * @param <A>
 *            Argument/change to be delivered to the observer
 */
public class Observable<A> {

    private transient boolean changed = false;
    private transient final List<Observer<? extends Observable<A>, A>> observers = Collections
            .synchronizedList(new ArrayList<Observer<? extends Observable<A>, A>>());

    /**
     * Adds an observer to the set of observers for this object, provided that
     * it is not the same as some observer already in the set. The order in
     * which notifications will be delivered to multiple observers is not
     * specified.
     * 
     * @param observer
     *            an observer to be added.
     * @throws IllegalArgumentException
     *             if the parameter o is null.
     */
    public void addObserver(final Observer<? extends Observable<A>, A> observer) {
        if (observer == null) {
            throw new IllegalArgumentException("Observer must not be null");
        }
        synchronized (observers) {

            if (!observers.contains(observer)) {
                observers.add(observer);
            }
        }
    }

    /**
     * Indicates that this object has no longer changed, or that it has already
     * notified all of its observers of its most recent change, so that the
     * <tt>hasChanged</tt> method will now return <tt>false</tt>. This method is
     * called automatically by the <code>notifyObservers</code> methods.
     * 
     * @see org.beyondhc.lessons.observer.genericwithtwoargs.Observable#notifyObservers()
     * @see org.beyondhc.lessons.observer.genericwithtwoargs.Observable#notifyObservers(java.lang.Object)
     */
    protected synchronized void clearChanged() { // NOPMD by kulmala on
                                                 // 16.10.2011 11:37: Boolean
                                                 // field synchronization is
                                                 // a bit tricky thing. As
                                                 // the original code has
                                                 // been working since Java
                                                 // 1.0, I am not touching
                                                 // this.
        changed = false;
    }

    /**
     * Returns the number of observers of this <tt>Observable</tt> object.
     * 
     * @return the number of observers of this object.
     */
    public int countObservers() {
        return observers.size();
    }

    /**
     * Deletes an observer from the set of observers of this object. Passing
     * <CODE>null</CODE> to this method will have no effect.
     * 
     * @param observer
     *            the observer to be deleted.
     */
    public void deleteObserver(
            final Observer<? extends Observable<A>, A> observer) {
        observers.remove(observer);
    }

    /**
     * Clears the observer list so that this object no longer has any observers.
     */
    public void deleteObservers() {
        observers.removeAll(observers);
    }

    /**
     * Tests if this object has changed.
     * 
     * @return <code>true</code> if and only if the <code>setChanged</code>
     *         method has been called more recently than the
     *         <code>clearChanged</code> method on this object;
     *         <code>false</code> otherwise.
     * @see org.beyondhc.lessons.observer.genericwithtwoargs.Observable#clearChanged()
     * @see jorg.beyondhc.util.Observable#setChanged()
     */
    public synchronized boolean hasChanged() { // NOPMD by kulmala on 16.10.2011
                                               // 11:38: Boolean field
                                               // synchronization is a bit
                                               // tricky thing. As the original
                                               // code has been working since
                                               // Java 1.0, I am not touching
                                               // this.
        return changed;
    }

    /**
     * If this object has changed, as indicated by the <code>hasChanged</code>
     * method, then notify all of its observers and then call the
     * <code>clearChanged</code> method to indicate that this object has no
     * longer changed.
     * <p>
     * Each observer has its <code>update</code> method called with two
     * arguments: this observable object and <code>null</code>. In other words,
     * this method is equivalent to: <blockquote><tt>
     * notifyObservers(null)</tt></blockquote>
     * 
     * @see org.beyondhc.lessons.observer.genericwithtwoargs.Observable#clearChanged()
     * @see org.beyondhc.lessons.observer.genericwithtwoargs.Observable#hasChanged()
     * @see org.beyondhc.lessons.observer.genericwithtwoargs.Observer#update(S,A)
     */
    public void notifyObservers() {
        notifyObservers(null);
    }

    /**
     * If this object has changed, as indicated by the <code>hasChanged</code>
     * method, then notify all of its observers and then call the
     * <code>clearChanged</code> method to indicate that this object has no
     * longer changed.
     * <p>
     * Each observer has its <code>update</code> method called with two
     * arguments: this observable object and the <code>arg</code> argument.
     * 
     * @param arg
     *            argument/change to be delivered to observers
     * 
     * @see org.beyondhc.lessons.observer.genericwithtwoargs.Observable#clearChanged()
     * @see org.beyondhc.lessons.observer.genericwithtwoargs.Observable#hasChanged()
     * @see org.beyondhc.lessons.observer.genericwithtwoargs.Observer#update(S,A)
     */
    @SuppressWarnings({ "rawtypes", "unchecked" })
    public void notifyObservers(final A arg) {
        /*
         * a temporary array buffer, used as a snapshot of the state of current
         * Observers.
         */

        Object[] arrLocal;
        synchronized (this) {
            /*
             * We don't want the Observer doing callbacks into arbitrary code
             * while holding its own Monitor. The code where we extract each
             * Observable from the Vector and store the state of the Observer
             * needs synchronization, but notifying observers does not (should
             * not). The worst result of any potential race-condition here is
             * that: 1) a newly-added Observer will miss a notification in
             * progress 2) a recently unregistered Observer will be wrongly
             * notified when it doesn't care
             */
            if (!changed) {
                return;
            }
            arrLocal = observers.toArray();
            clearChanged();
        }

        for (int i = arrLocal.length - 1; i >= 0; i--) {
            final Object observerObject = arrLocal[i];
            // Type erasure check is too tight to use generic types here
            if (observerObject instanceof Observer) {
                final Observer observer = (Observer) observerObject;
                observer.update(this, arg);
            }
        }
    }

    /**
     * Marks this <tt>Observable</tt> object as having been changed; the
     * <tt>hasChanged</tt> method will now return <tt>true</tt>.
     */
    protected synchronized void setChanged() { // NOPMD by kulmala on 16.10.2011
                                               // 11:38: Boolean field
                                               // synchronization is a bit
                                               // tricky thing. As the original
                                               // code has been working since
                                               // Java 1.0, I am not touching
                                               // this.
        changed = true;
    }
}
